local _M = {_VERSION = '0'}
local mt = {__index = _M}

local LOCK_EXPTIME = 10
local LOCK_TIMEOUT = 5
local DB_TIMEOUT = 5000
local DB_MAX_IDLE_TIME = 60000
local DB_POOL_SIZE = 100

local sky_db_util = require("skywalking.plugins.db_util")

local http_return = function(status,body)
	ngx.status = status
	if body then ngx.print(body) end
	ngx.exit(ngx.HTTP_OK)
end

function _M.new()
    local db, err = mysql:new()
    if not db then
        nlog.derror("failed to instantiate mysql: " .. err)
        http_return(500,"failed to instantiate mysql: " .. err)
    end

    local self = {mysql = db, l = nil, elapsed_lock = -1, locked = 0}
    return setmetatable(self, mt)
end

function _M.lock(self, key)
    local elapsed, err = self["l"]:lock(key)

    if nil == elapsed then
        nlog.derror("failed to get lock:" .. err)
        http_return(500,"failed to get lock:" .. err)
    end

    self.locked = 1
    self.elapsed_lock = elapsed
end

function _M.connect(self, db_param)
    local start = ngx.now()
    local res, err, errno, sqlstate = self["mysql"]:connect(db_param)
    local request_time = ngx.now() - start
    request_time = string.format("%.3f", request_time)

    self.db_host = db_param.host
    if not res then
        if self.locked == 1 then self:unlock() end
        -- 记录sql连接
        ifxlog.mysql(ngx.var.uri .. "(" .. ngx.var.remote_addr .. ")", 
            (errno or 500),        -- 状态
            request_time,          -- 执行耗时
            self.db_host,          -- 被访问的dbserver ip
            self.database .. ".",  -- 被访问的数据库.数据表
            (err or "")            -- sql操作类型
        )
        sky_db_util.MysqlAfter(self["mysql"], err)
        nlog.derror("failed to connect: " .. err.." "..cjson.encode(db_param))
        http_return(500,"failed to connect: " .. err)
    end
end

function _M.query(self, sql, nsock)
    local start = ngx.now()
    local res, err, errno, sqlstate = self["mysql"]:query(sql)
    self.elapsed_sql = ngx.now() - start
    local elapsed_sql = string.format("%.3f", self.elapsed_sql)

    if self.database then
        local regex = [=[^\s*(?<action>\w+)\s+(.*from\s+|.*FROM\s+|.*into\s+|.*INTO\s+)?(?<tbname>[a-zA-Z0-9_]+)\s+.*]=]
        local m, err = ngx.re.match(sql, regex)
        if m and m["action"] and m["tbname"] then
            nlog.recordSqlInfo(self.db_host, elapsed_sql, self.database, m["tbname"], string.lower(m["action"]))
            -- 记录sql 耗时
            if tonumber(elapsed_sql) > 0.2 then
                ifxlog.mysql(ngx.var.uri .. "(" .. ngx.var.remote_addr .. ")", 
                    200,                                           -- 状态
                    elapsed_sql,                                   -- 执行耗时
                    self.db_host,                                  -- 被访问的dbserver ip
                    self.database .. "." .. (m["tbname"] or ""),   -- 被访问的数据库.数据表
                    (string.lower(m["action"] or ""))              -- sql操作类型
                )
            end
        end
    end

    if string.len(sql) > 768 then
        nlog.sql(self.db_host, self.elapsed_lock, elapsed_sql, string.sub(sql, 1, 768) .. "...", nsock)
    else
        nlog.sql(self.db_host, self.elapsed_lock, elapsed_sql, sql, nsock)
    end

    if not res then
        nlog.derror("bad result: " .. err .. ":" .. sql)
    end

    sky_db_util.MysqlAfter(self.mysql, sql, err)
    return res,err, errno, sqlstate
end

function _M.unlock(self)
    if self.locked == 1 then
        local ok, err = self["l"]:unlock()
        if not ok then
            nlog.derror("failed to unlock: " .. err)
        else
            self.locked = 0
        end
    end
end

function _M.set_keepalive(self)
    local ok, err = self["mysql"]:set_keepalive(DB_MAX_IDLE_TIME, DB_POOL_SIZE)
    if not ok then
        if string.find(err, "cannot be reused in the current connection state") then
            nlog.dwarn("close sock, because " .. err)
            self["mysql"]:close()
        else
            nlog.derror("failed to set keepalive: " .. err)
        end
    end
end

function _M.set_timeout(self, timeout)
    self["mysql"]:set_timeout(timeout)
end

function _M.done(self, opts)
    local nolock = opts.nolock

    self:set_keepalive()
    if not nolock then
        self:unlock()
    end
end

function _M.init(self, opts)
    local nolock = opts.nolock
    local db_param = opts.db_param
    local key = opts.key
    local timeout = opts.timeout or db_param.timeout or DB_TIMEOUT
    sky_db_util.MysqlBefore(self.mysql, db_param)
    self:set_timeout(timeout)
    self["database"] = db_param.database
    if not nolock then
        local l, err = lock:new("db_lock", {exptime = LOCK_EXPTIME, timeout = LOCK_TIMEOUT})
        if not l then
            sky_db_util.MysqlAfter(self.mysql, "", err)
            nlog.derror("failed to instantiate lock: " .. err)
            http_return(500,"failed to instantiate lock: " .. err)
        end

        self["l"] = l
        self["elapsed_lock"] = -1
        self["locked"] = 0

        self:lock(key)
    end
    self:connect(db_param)
end

return _M
